#pragma once

#include <unordered_map>
#include <cstdint>
#include <cstddef>
#include <cstdint>
#include "../../corelib/singleton.hpp"
#include "../base/module_frame.hh"

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4100)
#pragma warning(disable: 4820)
#pragma warning(disable: 4625)
#pragma warning(disable: 4626)
#pragma warning(disable: 4514)
#pragma warning(disable: 4125)
#pragma warning(disable: 4244)
#pragma warning(disable: 4996)
#endif
#include "google/protobuf/message.h"
#ifdef _MSC_VER
#pragma warning(pop)
#endif

using namespace google::protobuf;

class MsgHandler;

// protocol register
class ProtocolRegister {
public:
    ~ProtocolRegister() {
        for (auto it : msgMap_) {
            kratos::corelib::dispose(it.second);
        }
        msgMap_.clear();
    }
    // register message ID and message handler
    // return true or false
    bool doRegister();
    // returns a protobuf::Message instance for msgID
    // @param msgID message ID
    // @return nullptr not found
    Message* getMessage(std::uint32_t msgID);
    // returns message ID for msg
    // @param msg protobuf::Message reference
    // @return message ID
    std::uint32_t getMessageID(const Message& msg);
    // returns the last error
    const std::string& getError() const;
    // registers {msgID, protobuf::Message}
    // @param msgID message ID
    // @retval true
    // @retval false invoke getError() for error string
    template <typename T>
    bool registerMessage(std::uint32_t msgID) {
        if (msgMap_.end() != msgMap_.find(msgID)) {
            errorlog << "Register message failed 'registerMessage', DUP message ID:" << msgID << endlog;
            return false;
        }
        msgMap_[msgID] = kratos::corelib::allocate<T>();
        typeMap_[reinterpret_cast<ptrdiff_t>(T::descriptor())] = msgID;
        return true;
    }
    // register {protobuf::Message, msgID}
    // @param msgID message ID
    // @retval true
    // @retval false invoke getError() for error string
    template <typename T>
    bool registerMessagePrototype(std::uint32_t msgID) {
        if (msgMap_.end() != msgMap_.find(msgID)) {
            errorlog << "Register message failed 'registerMessagePrototype', DUP message ID:" << msgID << endlog;
            return false;
        }
        typeMap_[reinterpret_cast<ptrdiff_t>(T::descriptor())] = msgID;
        return true;
    }
    // returns message ID for T
    // @return 0 not found
    template <typename T>
    std::uint32_t getMessageIDFromProto() {
        auto address = reinterpret_cast<ptrdiff_t>(T::descriptor());
        auto it = typeMap_.find(address);
        if (it == typeMap_.end()) {
            return 0;
        }
        return it->second;
    }
    // Returns message ID from google::protobuf::message
    inline std::uint32_t getMessageIDFromMessage(const google::protobuf::Message& msg) {
        auto address = reinterpret_cast<ptrdiff_t>(msg.GetDescriptor());
        auto it = typeMap_.find(address);
        if (it == typeMap_.end()) {
            return 0;
        }
        return it->second;
    }
    // registers message handler
    // @param handler message handler
    // @retval true
    // @retval false invoke getError() for error string
    template <typename T>
    bool registerHandler(MsgHandler* handler) {
        auto id = getMessageIDFromProto<T>();
        handlerMap_[id] = handler;
        return true;
    }
    // returns message handler for msgID
    // @param msgID message ID
    // @retval nullptr not found
    MsgHandler* getHandler(std::uint32_t msgID);

private:
    using MsgMap = std::unordered_map<std::uint32_t, Message*>;
    using TypeMap = std::unordered_map<ptrdiff_t, std::uint32_t>;
    using HandlerMap = std::unordered_map<std::uint32_t, MsgHandler*>;
    MsgMap msgMap_; // {message ID, protobuf::Message*}
    TypeMap typeMap_; // {protobuf::Message::descriptor(), message ID}
    HandlerMap handlerMap_; // message handlers
    std::string error_; // error string
};

#define ProtocolRegisterRef (*kratos::corelib::Singleton<ProtocolRegister>::instance())
#define ProtocolRegisterSingleton kratos::corelib::Singleton<ProtocolRegister>

// message handler interface
class MsgHandler {
public:
    // deal with message from grid
    // @param pathID path ID
    // @param siteType remote site type
    // @param msg protobuf::Message
    virtual void onPathMessage(PathID pathID, SiteType siteType, Message& msg) {
        pathID; siteType; msg;
    };
    // deal with message from session
    // @param sessionID session ID
    // @param msg protobuf::Message
    virtual void onSessionMessage(SessionID sessionID, Message& msg) {
        sessionID; msg;
    }
    // deal with message from entity
    // @param from source entity ID
    // @param to target entity ID
    // @param msg protobuf::Message
    virtual void onEntityMessage(std::uint64_t from, std::uint64_t to, Message&  msg) {
        from; to; msg;
    }
};

#define DECLARE_MSG_HANDLER(type)\
    class type##Handler : public MsgHandler {\
    public:\
        type##Handler() {}\
        virtual ~type##Handler() {}\
        virtual void onPathMessage(PathID pathID, SiteType siteType, Message& msg) override {\
            _onPathMessage(pathID, siteType, dynamic_cast<type&>(msg));\
        }\
        virtual void onSessionMessage(SessionID sessionID, Message& msg) override {\
            _onSessionMessage(sessionID, dynamic_cast<type&>(msg));\
        }\
        virtual void onEntityMessage(std::uint64_t from, std::uint64_t to, Message& msg) override {\
            _onEntityMessage(from, to, dynamic_cast<type&>(msg));\
        }\
        void _onPathMessage(PathID pathID, SiteType siteType, type& msg);\
        void _onSessionMessage(SessionID sessionID, type& msg);\
        void _onEntityMessage(std::uint64_t from, std::uint64_t to, type& msg);\
    };\
    extern type##Handler* default##type##Handler;

#define DEFINE_MSG_HANDLER(type)\
    type##Handler _##type##Handler;\
    type##Handler* default##type##Handler = &_##type##Handler;

#define REGISTER_MESSAGE(type, id)\
    if (!registerMessage<type>(id)) {\
        error_ = "Register "#type" failed";\
        return false;\
    }

#define REGISTER_MESSAGE_PROTOTYPE(type, id)\
    if (!registerMessagePrototype<type>(id)) {\
        error_ = "Register "#type" failed";\
        return false;\
    }

#define REGISTER_MSG_HANDLER(type)\
    if (!registerHandler<type>(default##type##Handler)) {\
        error_ = "Register message handler for "#type" failed";\
        return false;\
    }

// implements msg handler for the message from grid
// pathID path ID
// siteType grid type
// msg message from remote site of type 'type' derived from protobuf::Message
#define ON_MSG(type, pathID, siteType, msg)\
    void type##Handler::_onSessionMessage(SessionID, type&) {}\
    void type##Handler::_onEntityMessage(std::uint64_t, std::uint64_t, type&){}\
    void type##Handler::_onPathMessage(PathID pathID, SiteType siteType, type& msg)

// Dispatches message to specific object's method
#define ON_MSG_DISPATCH(method, type, get)\
    ON_MSG(type, pathID, siteType, msg) {\
        auto object = get;\
        if (object) {\
            object->method(msg);\
        }\
    }

// Dispatches path ID and message to specific object's method
#define ON_MSG_DISPATCH_ALL(method, type, get)\
    ON_MSG(type, pathID, siteType, msg) {\
        auto object = get;\
        if (object) {\
            object->method(pathID, msg);\
        }\
    }

// implements msg handler for the message from gateway session
// sessionID session ID
// msg message from remote session of type 'type' derived from protobuf::Message
#define ON_SESSION_MSG(type, sessionID, msg)\
    void type##Handler::_onPathMessage(PathID, SiteType, type&) {}\
    void type##Handler::_onEntityMessage(std::uint64_t, std::uint64_t, type&){}\
    void type##Handler::_onSessionMessage(SessionID sessionID, type& msg)

// implements msg handler for the message from entity
// entityID entity ID
// msg message from remote entity of type 'type' derived from protobuf::Message
#define ON_ENTITY_MSG(type)\
    void type##Handler::_onPathMessage(PathID, SiteType, type&) {}\
    void type##Handler::_onSessionMessage(SessionID, type&) {}\
    void type##Handler::_onEntityMessage(std::uint64_t from, std::uint64_t to, type& msg) {\
        EntityManagerRef.onMessage<type>(from, to, msg);\
    }
